Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
37 / 37
ProductNormalizer
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
6 / 6
11
100.00% covered (success)
100.00%
37 / 37
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 normalize
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
5 / 5
 supportsNormalization
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
2 / 2
 getAttributeCodesOfAncestors
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
15 / 15
 hasParent
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getAttributeCodesForOwnLevel
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
11 / 11
<?php
declare(strict_types=1);
namespace Akeneo\Pim\Enrichment\Component\Product\Normalizer\Indexing\ProductAndProductModel;
use Akeneo\Pim\Enrichment\Component\Product\EntityWithFamilyVariant\EntityWithFamilyVariantAttributesProvider;
use Akeneo\Pim\Enrichment\Component\Product\Model\EntityWithFamilyVariantInterface;
use Akeneo\Pim\Enrichment\Component\Product\Model\ProductInterface;
use Akeneo\Pim\Structure\Component\Model\AttributeInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
 * Normalize products to the 'indexing_product_and_product_model' format.
 *
 * @author    Samir Boulil <samir.boulil@akeneo.com>
 * @copyright 2017 Akeneo SAS (http://www.akeneo.com)
 * @license   http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */
class ProductNormalizer implements NormalizerInterface
{
    private const FIELD_ATTRIBUTES_OF_ANCESTORS = 'attributes_of_ancestors';
    private const FIELD_DOCUMENT_TYPE = 'document_type';
    private const FIELD_ATTRIBUTES_IN_LEVEL = 'attributes_for_this_level';
    /** @var NormalizerInterface */
    private $propertiesNormalizer;
    /** @var EntityWithFamilyVariantAttributesProvider */
    private $attributesProvider;
    /**
     * @param NormalizerInterface                       $propertiesNormalizer
     * @param EntityWithFamilyVariantAttributesProvider $attributesProvider
     */
    public function __construct(
        NormalizerInterface $propertiesNormalizer,
        EntityWithFamilyVariantAttributesProvider $attributesProvider
    ) {
        $this->propertiesNormalizer = $propertiesNormalizer;
        $this->attributesProvider = $attributesProvider;
    }
    /**
     * {@inheritdoc}
     */
    public function normalize($product, $format = null, array $context = [])
    {
        $data = $this->propertiesNormalizer->normalize($product, $format, $context);
        $data[self::FIELD_DOCUMENT_TYPE] = ProductInterface::class;
        $data[self::FIELD_ATTRIBUTES_OF_ANCESTORS] = $this->getAttributeCodesOfAncestors($product);
        $data[self::FIELD_ATTRIBUTES_IN_LEVEL] = $this->getAttributeCodesForOwnLevel($product);
        return $data;
    }
    /**
     * {@inheritdoc}
     */
    public function supportsNormalization($data, $format = null)
    {
        return ProductModelNormalizer::INDEXING_FORMAT_PRODUCT_AND_MODEL_INDEX === $format &&
            $data instanceof ProductInterface;
    }
    /**
     * Get the attribute codes of the family if product is not a variant.
     * If variant, get attribute codes of the family variant.
     *
     * We index all attribute codes to be able to search products on attributes with operators like "is empty".
     * At the end, we sort to reindex attributes correctly (if index keys are not sorted correctly, ES will throw an exception)
     *
     * @param ProductInterface $product
     *
     * @return array
     */
    private function getAttributeCodesOfAncestors(ProductInterface $product): array
    {
        if (!$product->isVariant()) {
            return [];
        }
        $ancestorsAttributesCodes = [];
        $entityWithFamilyVariant = $product;
        while ($this->hasParent($entityWithFamilyVariant)) {
            $parent = $entityWithFamilyVariant->getParent();
            $attributes = $this->attributesProvider->getAttributes($parent);
            $attributeCodes = array_map(
                function (AttributeInterface $attribute) {
                    return $attribute->getCode();
                },
                $attributes
            );
            $ancestorsAttributesCodes = array_merge($ancestorsAttributesCodes, $attributeCodes);
            $entityWithFamilyVariant = $parent;
        }
        sort($ancestorsAttributesCodes);
        return $ancestorsAttributesCodes;
    }
    private function hasParent(EntityWithFamilyVariantInterface $entityWithFamilyVariant): bool
    {
        return null !== $entityWithFamilyVariant->getParent();
    }
    /**
     * Get the attribute codes of the family if product is not a variant.
     * If variant, get attribute codes of the family variant.
     *
     * We index all attribute codes to be able to search products on attributes with operators like "is empty".
     * At the end, we sort to reindex attributes correctly (if index keys are not sorted correctly, ES will throw an exception)
     *
     * @param ProductInterface $product
     *
     * @return array
     */
    private function getAttributeCodesForOwnLevel(ProductInterface $product): array
    {
        $attributeCodes = array_keys($product->getRawValues());
        $familyAttributesCodes = [];
        if ($product->isVariant()) {
            $familyAttributes = $this->attributesProvider->getAttributes($product);
            $familyAttributesCodes = array_map(function (AttributeInterface $attribute) {
                return $attribute->getCode();
            }, $familyAttributes);
        } elseif (null !== $product->getFamily()) {
            $familyAttributesCodes = $product->getFamily()->getAttributeCodes();
        }
        $attributeCodes = array_unique(array_merge($familyAttributesCodes, $attributeCodes));
        sort($attributeCodes);
        return $attributeCodes;
    }
}